using System;
using System.Collections.Generic;
using System.Text;
using SavedVariablesParser.Line;

namespace SavedVariablesParser {
    /// <summary>
    /// Provides batch <see cref="Document"/> processing capabilities.
    /// </summary>
    public sealed class DocumentProcessor {
        private Queue<Document> documentQueue = new Queue<Document>();
        private static DocumentProcessor instance;
        private static object padlock = new object();
        private Document currentDocument;

        /// <summary>
        /// 	<para>Initializes an instance of the <see cref="DocumentProcessor"/> class.</para>
        /// </summary>
        private DocumentProcessor() { }

        /// <summary>
        /// Returns a singleton instance of an <see cref="DocumentProcessor"/> object.
        /// </summary>
        public static DocumentProcessor Instance {
            get {
                lock (padlock) {
                    if (instance == null) {
                        instance = new DocumentProcessor();
                    }
                    return instance;
                }
            }
        }

        /// <summary>
        /// Gets the number of <see cref="Document"/> objects in the processor queue.
        /// </summary>
        public int Count {
            get {
                return this.documentQueue.Count;
            }
        }

        /// <summary>
        /// Gets the <see cref="Document"/> that is currently being processed.
        /// </summary>
        public Document CurrentDocument {
            get {
                return this.currentDocument;
            }
        }

        /// <summary>
        /// Removes all of the <see cref="Document"/> objects from the processor queue.
        /// </summary>
        public void Clear() {
            this.documentQueue.Clear();
        }

        /// <summary>
        /// Adds the specified <see cref="Document"/> to the processor.
        /// </summary>
        /// <param name="document">The <see cref="Document"/> object to add to the processor queue.</param>
        /// <exception cref="ArgumentNullException">
        /// 	<para>The argument <paramref name="document"/> is <langword name="null"/>.</para>
        /// </exception>
        public void Add(Document document) {
            if (document == null) {
                throw new ArgumentNullException("document");
            }
            this.documentQueue.Enqueue(document);
        }

        /// <summary>
        /// Initiates <see cref="Document"/> processing.
        /// </summary>
        public void Process() {
            string savePath = string.Empty;
            while (this.documentQueue.Count > 0) {
                this.currentDocument = this.documentQueue.Dequeue();
                this.currentDocument.LineParsed += new Document.LineParsedEventHandler(document_LineParsed);

                this.OnDocumentStarted(this.currentDocument);
                savePath = this.currentDocument.SaveConvertedXML();
                this.OnDocumentCompleted(new DocumentCompletedEventArgs(this.currentDocument, savePath));
            }
        }

        #region document line parsed bubble event

        /// <summary>
        /// The event that is fired whenever a line is parsed by the <see cref="Document"/>.
        /// </summary>
        public event Document.LineParsedEventHandler LineParsed;

        /// <summary>
        /// Delegate method that is called when a documents LineParsed event is fired.  This method is responsible for bubbling the event up through the processor.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="line">An <see cref="ILine"/> object that represents the data from the event.</param>
        private void document_LineParsed(Document sender, ILine line) {
            this.OnLineParsed(sender, line); // bubble the event.
        }

        /// <summary>
        /// Bubbles the Document.LineParsed event up from the base document object event.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="line">An <see cref="ILine"/> object that represents the data from the event.</param>
        private void OnLineParsed(Document sender, ILine line) {
            if (LineParsed != null && sender != null && line != null) {
                LineParsed(sender, line);
            }
        }

        #endregion

        #region document processing events

        /// <summary>
        /// Represents the method that will handle the DocumentStarted event of the <see cref="DocumentProcessor"/>.
        /// </summary>
        /// <param name="sender">The object that initiated the event.</param>
        /// <param name="document">The <see cref="Document"/> that was started.</param>
        public delegate void DocumentStartedEventHandler(object sender, Document document);

        /// <summary>
        /// Represents the method that will handle the DocumentCompleted event of the <see cref="DocumentProcessor"/>.
        /// </summary>
        /// <param name="sender">The object that initiated the event.</param>
        /// <param name="e">The <see cref="DocumentCompletedEventArgs"/> that contains event data.</param>
        public delegate void DocumentCompletedEventHandler(object sender, DocumentCompletedEventArgs e);

        /// <summary>
        /// Fires when a <see cref="Document"/> has completed its processing.
        /// </summary>
        public event DocumentCompletedEventHandler DocumentCompleted;

        /// <summary>
        /// Fires when a <see cref="Document"/> has started processing.
        /// </summary>
        public event DocumentStartedEventHandler DocumentStarted;

        /// <summary>
        /// Fires the <see cref="DocumentStarted"/> event of the <see cref="DocumentProcessor"/>.
        /// </summary>
        /// <param name="document">The <see cref="Document"/> that was started.</param>
        private void OnDocumentStarted(Document document){
            if (DocumentStarted != null && document != null) {
                DocumentStarted(this, document);
            }
        }

        /// <summary>
        /// Fires the <see cref="DocumentCompleted"/> event of the <see cref="DocumentProcessor"/>.
        /// </summary>
        /// <param name="e">The <see cref="DocumentCompletedEventArgs"/> that contains event data.</param>
        private void OnDocumentCompleted(DocumentCompletedEventArgs e) {
            if (DocumentCompleted != null && e != null) {
                DocumentCompleted(this, e);
            }
        }

        /// <summary>
        /// Provides information about a <see cref="DocumentProcessor.DocumentCompleted"/> event.
        /// </summary>
        public class DocumentCompletedEventArgs : System.EventArgs{
            private Document document = null;
            private string savePath = string.Empty;

            /// <summary>
            /// 	<para>Initializes an instance of the <see cref="DocumentCompletedEventArgs"/> class.</para>
            /// </summary>
            /// <param name="document">The document that was completed.</param>
            /// <param name="savePath">The path that the document was saved to.</param>
            /// <exception cref="ArgumentNullException">
            /// 	<para>The argument <paramref name="document"/> is <langword name="null"/>.</para>
            /// 	<para>-or-</para>
            /// 	<para>The argument <paramref name="savePath"/> is <langword name="null"/>.</para>
            /// </exception>
            /// <exception cref="ArgumentOutOfRangeException">
            /// 	<para>The argument <paramref name="savePath"/> is out of range.</para>
            /// </exception>
            public DocumentCompletedEventArgs(Document document, string savePath) {
                if (document == null) {
                    throw new ArgumentNullException("document");
                }
                if (savePath == null) {
                    throw new ArgumentNullException("savePath");
                }
                if (savePath.Length == 0) {
                    throw new ArgumentOutOfRangeException("savePath", "The save path specified cannot be a zero length string");
                }
                this.document = document;
                this.savePath = savePath;
            }
            
            /// <summary>
            /// Gets or Sets the document that was completed.
            /// </summary>
            public Document Document {
                get {
                    return this.document;
                }
                set {
                    this.document = value;
                }
            }
            
            /// <summary>
            /// Gets or Sets the path that the document was saved to.
            /// </summary>
            public string SavePath {
                get {
                    return this.savePath;
                }
                set {
                    this.savePath = value;
                }
            }
        }

        #endregion
    }
}